/*
 * JANN - a Java toolkit for creating arbitrary Artificial Neural Networks.
 * 
 * Copyright (c) 2009 Matthijs Snel
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.geom.Point2D;
import java.util.List;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import net.jann.Function;
import net.jann.InputLayer;
import net.jann.Layer;
import net.jann.LayerLink;
import net.jann.LayeredNeuralNet;
import net.jann.LinearFunction;
import net.jann.OutputLayer;
import net.jann.Parameters;
import net.jann.SigmoidFunction;
import net.jann.Trainer;
import net.jann.TrainingItem;
import net.jann.ui.LayerFigure;
import net.jann.ui.LinkFigure;
import net.jann.ui.NeuronSelectionTool;

import org.jhotdraw.draw.*;
import org.jhotdraw.geom.Geom;

public class Test implements ActionListener {

	static LayeredNeuralNet nn;
	static Trainer trainer;
	
	public Test() {
		
		Function f = new LinearFunction();
		LayeredNeuralNet net1 = LayeredNeuralNet.createStandardNet(1, 2, 1, f);
		Parameters params = new Parameters();
		params.set(Parameters.RNG, new Random(500));
		params.set(Parameters.LEARNING_RATE, new Double(0.05));
		params.set(Parameters.MOMENTUM, new Double(0.5));
		net1.setParameters(params);
		net1.initWeights();
		
		double[][] trainingInput = new double[20][1];
		for ( int i = 0; i < trainingInput.length; i++ )
			trainingInput[i][0] = i;//i*0.1*Math.PI;
		
		double[][] trainingTarget = new double[trainingInput.length][1];
		for ( int i = 0; i < trainingTarget.length; i++ )
			trainingTarget[i][0] = Math.sin(trainingInput[i][0]);
		
		Trainer trainer = new Trainer();
		for ( int i = 0; i < trainingInput.length; i++ )
			trainer.addTrainingItem( new TrainingItem( trainingInput[i], trainingTarget[i] ));

		trainer.setTrainee(net1);
		
		//trainer.train(2000);
		//trainer.printSSE();
		
		Trainer cosTrainer = new Trainer();
		for ( int i = 0; i < trainingInput.length; i++ ) {
			cosTrainer.addTrainingItem( new TrainingItem( trainingInput[i], new double[]{trainingInput[i][0]*trainingInput[i][0]} ));
			System.out.println(trainingInput[i][0]*trainingInput[i][0]);
		}
		cosTrainer.setTrainee(net1);
		cosTrainer.train(1000);
		cosTrainer.printSSE();
		
		System.out.println("\n" + net1.weightsToString() + "\n");
		
		for ( int i = 0; i < trainingInput.length; i++ ) {
			net1.feedforward(trainingInput[i]);
			System.out.println(trainingInput[i][0] + " " + trainingTarget[i][0] + " " + net1.getOutput()[0]);
		}
	}
	
	public static void main( String[] args ) {
		SwingUtilities.invokeLater(new Runnable() {
            public void run() {
            	new Test();
            }
		});
	}
	
	public static void createNeuralNet() {
		InputLayer in = new InputLayer(2);
		SigmoidFunction f = new SigmoidFunction();
		Layer hid = new Layer(3, f);
		Layer out = new Layer(1, f);
		LayerLink inhid = new LayerLink(in, hid);
		LayerLink hidout = new LayerLink(hid, out);
		
		LayeredNeuralNet nn = new LayeredNeuralNet();
		nn.addLayer(in);
		nn.addLayer(out);
		
		double[] test = {1, 1};
		nn.feedforward(test);
		double[] output = nn.getOutput();
		System.out.println("Output: " + output[0]);
	}
	
	public static void createLinks( LayerFigure from, LayerFigure to, Drawing drawing ) {
		SlantedLiner liner = new SlantedLiner(1);
		LocatorLayouter layouter = new LocatorLayouter();
		LinkFigure linkFig;
		
		for ( Figure fromFig : from.getChildren() )
			for ( Figure toFig : to.getChildren() ) {
				linkFig = new LinkFigure();
            	linkFig.setLiner(liner);
            	linkFig.setLayouter(layouter);
            	
            	linkFig.setStartConnector(fromFig.findConnector(Geom.center(fromFig.getBounds()), linkFig));
            	linkFig.setEndConnector(toFig.findConnector(Geom.center(toFig.getBounds()), linkFig));
            	
            	drawing.add(linkFig);
			}
	}
	
	public static void testNeuralNet() {
		InputLayer in = new InputLayer(2);
		SigmoidFunction fun = new SigmoidFunction();
		Parameters params = new Parameters();
		params.set(Parameters.RNG, new Random(100));
		params.set(Parameters.LEARNING_RATE, new Double(0.1));
		params.set(Parameters.MOMENTUM, new Double(0.5));
		Layer hid = new Layer(2, fun);
		OutputLayer out = new OutputLayer(1, fun);
		LayerLink inhid = new LayerLink(in, hid);
		LayerLink hidout = new LayerLink(hid, out);
		//TODO: diff way of doing this
		hid.setParameters(params);
		out.setParameters(params);
		inhid.setParameters(params);
		hidout.setParameters(params);
		hid.initWeights();
		out.initWeights();
		inhid.initWeights();
		hidout.initWeights();
		
		LayeredNeuralNet nn = new LayeredNeuralNet();
		nn.addLayer(in);
		nn.addLayer(out);
		
		Trainer trainer = new Trainer();
		trainer.addTrainingItem( new TrainingItem( new double[]{0, 0}, new double[]{0}) );
		trainer.addTrainingItem( new TrainingItem( new double[]{0, 1}, new double[]{1}) );
		trainer.addTrainingItem( new TrainingItem( new double[]{1, 0}, new double[]{1}) );
		trainer.addTrainingItem( new TrainingItem( new double[]{1, 1}, new double[]{0}) );
		trainer.setTrainee(nn);
		
		trainer.train(1000);
		
		trainer.showNext();
		trainer.showNext();
		trainer.showNext();
		trainer.showNext();
	}
	
	public void testNeuralNetDisplay() {
		//testNeuralNet();
    	Drawing drawing = new DefaultDrawing();
    	
    	InputLayer in = new InputLayer(2);
		SigmoidFunction fun = new SigmoidFunction();
		Parameters params = new Parameters();
		params.set(Parameters.RNG, new Random(500));
		params.set(Parameters.LEARNING_RATE, new Double(0.1));
		params.set(Parameters.MOMENTUM, new Double(0.5));
		Layer hid = new Layer(2, fun);
		OutputLayer out = new OutputLayer(1, fun);
		LayerLink inhid = new LayerLink(in, hid);
		LayerLink hidout = new LayerLink(hid, out);
		//TODO: diff way of doing this
		hid.setParameters(params);
		out.setParameters(params);
		inhid.setParameters(params);
		hidout.setParameters(params);
		hid.initWeights();
		out.initWeights();
		inhid.initWeights();
		hidout.initWeights();
		
		LayerFigure layerFig = new LayerFigure(in);
		layerFig.setStartPoint(100, 200);
		drawing.add(layerFig);
		
		LayerFigure layerFig2 = new LayerFigure(hid);
		layerFig2.setStartPoint(50, 100);
		drawing.add(layerFig2);
		
		createLinks(layerFig, layerFig2, drawing);
		
		layerFig = new LayerFigure(out);
		layerFig.setStartPoint(150, 0);
		drawing.add(layerFig);
		
		createLinks(layerFig2, layerFig, drawing);

    	/*NeuronFigure 	fig1 = new NeuronFigure(10, 200),         	
    					fig2 = new NeuronFigure(90, 200),
    					fig3 = new NeuronFigure(50, 100);

    	drawing.add(fig1);
        drawing.add(fig2);
        drawing.add(fig3);
        
    	LinkFigure linkFig = new LinkFigure();
    	linkFig.setLiner(new SlantedLiner(1));
    	linkFig.setLayouter(new LocatorLayouter());
    	
    	linkFig.setStartConnector(fig1.findConnector(Geom.center(fig1.getBounds()), linkFig));
    	linkFig.setEndConnector(fig3.findConnector(Geom.center(fig3.getBounds()), linkFig));
    	
    	drawing.add(linkFig);
    	
    	linkFig = new LinkFigure();
    	linkFig.setLiner(new SlantedLiner(1));
    	linkFig.setLayouter(new LocatorLayouter());
    	
    	linkFig.setStartConnector(fig2.findConnector(Geom.center(fig2.getBounds()), linkFig));
    	linkFig.setEndConnector(fig3.findConnector(Geom.center(fig3.getBounds()), linkFig));
        
    	drawing.add(linkFig);*/
    	
        // Show the drawing
		
        JFrame f = new JFrame("Neural Network Visualiser");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(400,300);
        f.getContentPane().setLayout( new BorderLayout() );
        
        DrawingView view = new DefaultDrawingView();
        view.setDrawing(drawing);
        f.getContentPane().add(view.getComponent(), BorderLayout.CENTER);
        
        DrawingEditor editor = new DefaultDrawingEditor();
        editor.add(view);
        editor.setTool(new NeuronSelectionTool());
        
        JPanel buttonPanel = new JPanel( new GridLayout(2, 1) );
        
        JButton aButton = new JButton("Train!");
        aButton.addActionListener( this );
        buttonPanel.add(aButton);
        
        aButton = new JButton("Single step");
        aButton.addActionListener( this );
        buttonPanel.add(aButton);
        
        f.getContentPane().add(buttonPanel, BorderLayout.EAST);
        
        f.setVisible(true);
        
        nn = new LayeredNeuralNet();
		nn.addLayer(in);
		nn.addLayer(out);
		
		trainer = new Trainer();
		trainer.addTrainingItem( new TrainingItem( new double[]{0, 0}, new double[]{0}) );
		trainer.addTrainingItem( new TrainingItem( new double[]{0, 1}, new double[]{1}) );
		trainer.addTrainingItem( new TrainingItem( new double[]{1, 0}, new double[]{1}) );
		trainer.addTrainingItem( new TrainingItem( new double[]{1, 1}, new double[]{0}) );
		trainer.setTrainee(nn);
	}
	
	public void actionPerformed( ActionEvent e ) {
		if ( e.getActionCommand() == "Train!") {
			trainer.train(10000);
			//trainer.printSSE();
		} else {
			trainer.showNext();
		}
	}
}
